using System;
using System.Collections.Generic;
using System.Text;
using Facebook.Service;
using Facebook.Service.Core;
using System.Configuration;
using Microsoft.Practices.EnterpriseLibrary.PolicyInjection;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using System.Web;
using System.Web.Caching;
using FriendsNearby.Services.Entities;

namespace FriendsNearby.Services.Facebook
{
	public class FacebookSocialNetworkService : ISocialNetworkService
	{
		IDataAccess dataAccess;
		IUsersService userService;
        IFacebookServiceFactory facebookServiceFactory;

        public FacebookSocialNetworkService(IDataAccess dataAccess, IUsersService userService, IFacebookServiceFactory facebookServiceFactory)
		{
            this.dataAccess = dataAccess;
			this.userService = userService;
            this.facebookServiceFactory = facebookServiceFactory;
		}

		#region ISocialNetworkService Members

		public IEnumerable<Guid> GetFriends(Guid userId)
		{
            try
            {
				string facebookUserID = ConvertToFacebookID(userId);
                IFacebookService fs = GetImpersonatedFacebookService(facebookUserID);

                if (fs != null)
                {
                    var result = new List<string>(fs.GetFriends(FriendFilter.AppUsers));
					var converted = result.ConvertAll(x => ConvertToUserID(x));
                    converted.RemoveAll(y => y == Guid.Empty);
                    return converted;
                }
                else
                {
					return new List<Guid>();
                }
            }
            catch (Exception e)
            {
                LogEntry logEntry = new LogEntry();
                logEntry.Message = String.Format(Properties.Resources.UnableToQueryFriends, userId, e);
                logEntry.Categories.Add("Oddities");
                logEntry.EventId = LogEventIds.UnableToQueryFriendGraph;

                Logger.Write(logEntry);
				return new List<Guid>();
            }
		}

		private Guid ConvertToUserID(string facebookUserID)
		{
			UserRegistry user = dataAccess.QueryUserRegistry(x => x.FacebookUserId == facebookUserID);
            if (user == null)
                return Guid.Empty;
            else
    			return user.UserID;
		}

		private string ConvertToFacebookID(Guid userId)
		{
			UserRegistry user = dataAccess.QueryUserRegistry(x => x.UserID == userId);
			string facebookUserID = user.FacebookUserId;
			return facebookUserID;
		}

		public IEnumerable<Guid> GetFriendsOfFriends(Guid userId)
		{
			var friendsSet = new HashSet<Guid>(GetFriends(userId));
			var returnedFoafs = new HashSet<Guid>();

			foreach (Guid friendId in friendsSet)
			{
				foreach (Guid foafId in GetFriends(friendId))
				{
                    // neither me, nor my friends, nor a previous returned foaf
                    if (foafId != userId && !friendsSet.Contains(foafId) && !returnedFoafs.Contains(foafId))
                    {
                        returnedFoafs.Add(foafId);
                        yield return foafId;
                    }
				}
			}
		}

		public IEnumerable<Guid> GetFriendsInCommon(Guid userId1, Guid userId2)
		{
			HashSet<Guid> myFriends = new HashSet<Guid>(GetFriends(userId1));

			foreach (Guid hisFriend in GetFriends(userId2))
			{
                if (myFriends.Contains(hisFriend)){
                    yield return hisFriend;
				}
			}
		}

		public void SendIntrodutionNotification(string personalMessage, Guid senderUserID, Guid friendOfFriendUserID, Guid mutualFriendUserID)
        {
            string senderFacebookID = ConvertToFacebookID(senderUserID);
			string mutualContactFacebookID = ConvertToFacebookID(mutualFriendUserID);
			string contactOfContactFacebookID = ConvertToFacebookID(friendOfFriendUserID);

			SendImpersonatedNotification(contactOfContactFacebookID,
     string.Format(
@"is currently in the neighborhood of <fb:name uid=""{0}"" />, who found <fb:name uid=""{1}"" /> using the Contacts Nearby Application. 
<br/> <fb:name uid=""{0}"" /> has requested to get in touch with <fb:name uid=""{1}"" /> through you. 
<br/><br/>His personal message : <br/> {2}", senderFacebookID, contactOfContactFacebookID, personalMessage),
	 mutualContactFacebookID);

			SendImpersonatedNotification(mutualContactFacebookID,
                string.Format(
@"knows <fb:name uid=""{0}"" />, who found you in the neighborhood using the Contacts Nearby Application.
<br/> <fb:name uid=""{0}"" /> has requested an introduction to you, through your mutual contact <fb:name uid=""{1}"" />.
<br/><br/>Personal message : <br/> {2}", senderFacebookID, mutualContactFacebookID, personalMessage),
				contactOfContactFacebookID);
        }

        #endregion

        private void SendImpersonatedNotification(string senderFacebookID, string notification, string recipientFacebookID)
        {
			IFacebookService facebook = GetImpersonatedFacebookService(senderFacebookID);
            
            if (facebook != null)
				facebook.SendNotification(recipientFacebookID, notification, null);
        }

		public void RegisterFacebookUser(string facebookUserID, string sessionKey)
		{
            // check if user was already registered
            var oldRegistry = userService.QueryUserRegistry(x => x.FacebookUserId == facebookUserID);
            
            Guid userID;
            if (oldRegistry == null)
                userID = userService.RegisterUser();
            else
                userID = oldRegistry.UserID;

            IFacebookService fs = facebookServiceFactory.Create(ConfigHelper.FacebookApplicationKey,
                ConfigHelper.FacebookSecretKey,
                sessionKey,
                facebookUserID);
			dataAccess.AddFacebookIdentity(userID, facebookUserID, sessionKey, fs.UserName);
		}

        /// <summary>
        /// Makes sure that user is registered. if user was not registered or somehow doesnt exist
        /// in the db, the registry will be added ad-hoc.
        /// </summary>
        public void EnsureFacebookUserExists(string facebookUserID, string sessionKey)
        {
            IFacebookService fs = facebookServiceFactory.Create(ConfigHelper.FacebookApplicationKey,
                ConfigHelper.FacebookSecretKey,
                sessionKey,
                facebookUserID);

            bool justCreated = dataAccess.EnsureUserExists(facebookUserID, sessionKey, fs.UserName);
            if (justCreated)
            {
                var reg = userService.QueryUserRegistry(r => r.FacebookUserId == facebookUserID);
                userService.SetDefaultUserPreferences(reg.UserID);
            }
        }

		IFacebookService anyFacebookService;

		private IFacebookService GetAnyFacebookService(string facebookUserID)
		{
			if (anyFacebookService == null)
			{
				anyFacebookService = GetImpersonatedFacebookService(facebookUserID);
			}
			return anyFacebookService;
		}

		private IFacebookService GetImpersonatedFacebookService(string facebookUserID)
		{
			if (anyFacebookService != null && anyFacebookService.UserID == facebookUserID)
			{
				return anyFacebookService;
			}
			else
			{
				try
				{
					string userSessionKey = dataAccess.GetUserSessionKey(facebookUserID);

					IFacebookService fs = facebookServiceFactory.Create(ConfigHelper.FacebookApplicationKey,
								ConfigHelper.FacebookSecretKey,
								userSessionKey,
								facebookUserID);

					Logger.Write(String.Format("Creating facebook service with ApplicationKey:{0} and SecretKey: {1} "
									+ "for userId: {2} with SessionKey: {3}", ConfigHelper.FacebookApplicationKey,
									ConfigHelper.FacebookSecretKey, facebookUserID, userSessionKey));

					// force request to check FacebookService connection
					if (!fs.IsValid())
					{
						LogEntry logEntry = new LogEntry();
						logEntry.Message = String.Format(Properties.Resources.ErrorLog, facebookUserID);
						logEntry.Categories.Add("Oddities");
						logEntry.EventId = LogEventIds.UnableToEstablishSessionForUser;

						Logger.Write(logEntry);
						fs = null;
					}
					
					return anyFacebookService = fs;

				}
				catch (Exception ex)
				{
					LogEntry logEntry = new LogEntry();
					logEntry.Message = String.Concat(String.Format(Properties.Resources.ErrorLog, facebookUserID), "\nException:", ex.Message, "\nFull:", ex);
					logEntry.Categories.Add("Oddities");
					logEntry.EventId = LogEventIds.UnableToEstablishSessionForUserException;

					Logger.Write(logEntry);
					return null;
				}
			}
		}
	}
}
